#!/usr/bin/perl -w
#
# $Id: NetworkTree.pm,v 1.92 2005/04/11 21:58:28 solovam Exp $
#
# This file is a part of gkismet
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#

#
# NetworkTree class
#
package NetworkTree;

use Gtk;
use strict;
use Misc;
use Observable;
use ReusableConnObserver;

@NetworkTree::ISA = qw(Observable ReusableConnObserver);

my %network_type = (network_ap => 0, network_adhoc => 1, network_probe => 2, network_turbocell => 3, network_data => 4, network_remove => 5);
my %address_type = (address_none => 0, address_factory => 1, address_udp => 2, address_arp => 3, address_tcp => 4, address_dhcp => 5, address_group => 6);
my %carrier_type = (carrier_unknown => 0, carrier_80211b => 1, carrier_80211bplus => 2, carrier_80211a => 3, carrier_80211g => 4,
	carrier_80211fhss => 5, carrier_80211dsss => 6);
my %client_type = (client_unknown => 0, client_fromds => 1, client_tods => 2, client_interds => 3, client_established => 4);
my %encoding_type = (encoding_unknown => 0, encoding_cck => 1, encoding_pbcc => 2, encoding_ofdm => 3);
my %encryption_type = (encryption_none => 0, encryption_unknown => 1, encryption_wep => 2, encryption_layer3 => 4, encryption_wpa => 64,
	encryption_leap => 128, encryption_ttls => 256, encryption_tls => 512, encryption_peap => 1024, encryption_isakmp => 2048, encryption_pptp => 4096);
my $max_manuf_score = 8;
my $timerTimeout = 1000;
my $activeTimeout = 5;
my %networkFieldType = (channel => 'num', firsttime => 'num', lasttime => 'num', datapackets => 'num', signal => 'num', bssid => 'str',
	ssid => 'str', wep => 'str');
my %clientFieldType = (firsttime => 'num', lasttime => 'num', datapackets => 'num', signal => 'num', mac => 'str', wep => 'str');

#
# Constructor
#
sub new
{
	my $type = shift;
	my $self = new ReusableConnObserver(@_);
	bless $self, $type;

	# Create ctree
	$self->{'ctree'} = new Gtk::CTree(3, 0);
	$self->{'ctree'}->set_column_auto_resize(0, $true);
	$self->{'ctree'}->set_column_auto_resize(1, $true);
	$self->{'ctree'}->set_line_style('dotted');
	$self->{'ctree'}->set_indent(15); 
	$self->{'ctree'}->set_spacing(5);
	$self->{'ctree'}->set_selection_mode(1);
	$self->{'ctree'}->set_column_visibility(2, $false);
	$self->{'ctree'}->set_sort_column(2);
	$self->{'ctree'}->set_compare_func(sub {$self->compare(@_)});
	$self->{'ctree'}->signal_connect('tree_select_row', sub {$self->selectHandler(@_)});
	$self->{'ctree'}->signal_connect('tree_unselect_row', sub {$self->selectHandler(@_)});
	$self->{'ctree'}->show();

	# Read manufacturers data
	$self->{'apManuf'} = $self->readManufData($self->{'gKismetApplication'}{'path'}{'etc'} . '/ap_manuf');
	$self->{'clManuf'} = $self->readManufData($self->{'gKismetApplication'}{'path'}{'etc'} . '/client_manuf');

	# Get colors
        $self->buildColors();

	# Ask to notify us of the preferences changes
	$self->{'gKismetApplication'}->{'preferences'}->addObserver($self);

	# Popup menu
	my @menuItems =
	(
                {path => '/Expand', callback => sub {$self->expand()}},
                {path => '/Collapse', callback => sub {$self->collapse()} },
                {path => '/Packet Dump', callback => sub {$self->{'gKismetApplication'}->bssObserverOpenSelected('PacketWindow')}},
                {path => '/String Dump', callback => sub {$self->{'gKismetApplication'}->bssObserverOpenSelected('StringWindow')}},
                {path => '/Locator', callback => sub {$self->{'gKismetApplication'}->bssObserverOpenSelected('Locator')}},
		{path => '/Sort', type  => '<Branch>'},
		{path => '/Sort/Ascending', callback  => sub {$self->setSortOrder('ascending')}},
		{path => '/Sort/Descending', callback => sub {$self->setSortOrder('descending')}},
		{path => '/Sort Networks', type  => '<Branch>'},
		{path => '/Sort Networks/By channel', callback => sub {$self->setNetworksSortBy('channel')}},
		{path => '/Sort Networks/By first seen', callback => sub {$self->setNetworksSortBy('firsttime')}},
		{path => '/Sort Networks/By last seen', callback => sub {$self->setNetworksSortBy('lasttime')}},
		{path => '/Sort Networks/By BSSID', callback => sub {$self->setNetworksSortBy('bssid')}},
		{path => '/Sort Networks/By SSID', callback => sub {$self->setNetworksSortBy('ssid')}},
		{path => '/Sort Networks/By data packet count', callback => sub {$self->setNetworksSortBy('datapackets')}},
		{path => '/Sort Networks/By signal power', callback => sub {$self->setNetworksSortBy('signal')}},
		{path => '/Sort Networks/By WEP', callback => sub {$self->setNetworksSortBy('wep')}},
		{path => '/Sort Clients', type  => '<Branch>'},
		{path => '/Sort Clients/By first seen', callback => sub {$self->setClientsSortBy('firsttime')}},
		{path => '/Sort Clients/By last seen', callback => sub {$self->setClientsSortBy('lasttime')}},
		{path => '/Sort Clients/By MAC', callback => sub {$self->setClientsSortBy('mac')}},
		{path => '/Sort Clients/By data packet count', callback => sub {$self->setClientsSortBy('datapackets')}},
		{path => '/Sort Clients/By signal power', callback => sub {$self->setClientsSortBy('signal')}},
		{path => '/Sort Clients/By WEP', callback => sub {$self->setClientsSortBy('wep')}},
	);
	my $accelGroup = new Gtk::AccelGroup();
	my $itemFactory = new Gtk::ItemFactory('Gtk::Menu', '<main>', $accelGroup);
	$itemFactory->create_items(@menuItems);
	my $popup = $itemFactory->get_widget('<main>');
	$popup->show_all();
	$self->{'popup'} = $popup;
	$self->{'ctree'}->signal_connect('button_press_event', sub {$self->buttonPressHandler(@_)});

	# Sorting
	$self->{'ctree'}->set_sort_type(0);
	$self->{'networksSortBy'} = 'firsttime';
	$self->{'clientsSortBy'} = 'firsttime';

	# Set timer
	Gtk->timeout_add($timerTimeout, sub {$self->timerHandler()});

	# Pixmaps
	$self->{'pixmapsBuilt'} = $false;

	# Standard
	return $self;
}

#
# Set sorting order
#
sub setSortOrder
{
	my $self = shift;
	my $order = shift;
	if($order eq 'ascending')
	{
		$self->{'ctree'}->set_sort_type(0);
	}
	else
	{
		$self->{'ctree'}->set_sort_type(1);
	}
	$self->{'ctree'}->freeze();
	$self->{'ctree'}->sort_recursive(undef);
	$self->{'ctree'}->thaw();
}

#
# Handle tinmer event
#
sub timerHandler
{
	my $self = shift;

	if(!defined $self->{'connection'})
	{
		return $true;
	}

	$self->{'ctree'}->freeze();
	for my $bssid (keys %{$self->{'cnode'}})
	{
		my @info = $self->{'ctree'}->get_node_info($self->{'cnode'}{$bssid}{'node'});
		$self->{'ctree'}->set_node_info($self->{'cnode'}{$bssid}{'node'},
			$self->getNetworkFieldValue($bssid, 'SSID'), 5,
			$self->{'pixmap'}{$self->getNetworkPixmapName($bssid)}{'pixmap'},
			$self->{'pixmap'}{$self->getNetworkPixmapName($bssid)}{'mask'},
			$self->{'pixmap'}{$self->getNetworkPixmapName($bssid)}{'pixmap'},
			$self->{'pixmap'}{$self->getNetworkPixmapName($bssid)}{'mask'},
			$false, $info[7]);
		$self->{'ctree'}->node_set_foreground($self->{'cnode'}{$bssid}{'node'}, $self->getNetworkColor($bssid));
		for my $mac (keys %{$self->{'cnode'}{$bssid}{'client'}})
		{
			my @info = $self->{'ctree'}->get_node_info($self->{'cnode'}{$bssid}{'client'}{$mac}{'node'});
			$self->{'ctree'}->set_node_info($self->{'cnode'}{$bssid}{'client'}{$mac}{'node'},
				$self->getClientFieldValue($bssid, $mac, 'MAC'), 5,
				$self->{'pixmap'}{$self->getClientPixmapName($bssid, $mac)}{'pixmap'},
				$self->{'pixmap'}{$self->getClientPixmapName($bssid, $mac)}{'mask'},
				$self->{'pixmap'}{$self->getClientPixmapName($bssid, $mac)}{'pixmap'},
				$self->{'pixmap'}{$self->getClientPixmapName($bssid, $mac)}{'mask'},
				$false, $info[7]);
		}
	}
	if($self->{'gKismetApplication'}{'preferences'}->getPref('keepSorted'))
	{
		$self->sortUpdated();
	}
	$self->{'ctree'}->thaw();
	return $true;
}

#
# Sort updated items in ctree
#
sub sortUpdated
{
	my $self = shift;

	if(!defined $self->{'connection'})
	{
		return;
	}

	if($self->{'networkUpdateFlag'})
	{
		$self->{'ctree'}->sort(undef);
	}
	$self->{'networkUpdateFlag'} = $false;

	for my $bssid (keys %{$self->{'cnode'}})
        {
		if($self->{'clientUpdateFlag'}{$bssid})
		{
			$self->{'ctree'}->sort($self->{'cnode'}{$bssid}{'node'});
		}
		$self->{'clientUpdateFlag'}{$bssid} = $false;
	}
}

#
# Change network sorting
#
sub setNetworksSortBy
{
	my $self = shift;
	my $networksSortBy = shift;
	$self->{'ctree'}->freeze();
	$self->{'networksSortBy'} = $networksSortBy;
	$self->{'ctree'}->sort_recursive(undef);
	$self->{'ctree'}->thaw();
}

#
# Change client sorting
#
sub setClientsSortBy
{
	my $self = shift;
	my $clientsSortBy = shift;
	$self->{'ctree'}->freeze();
	$self->{'clientsSortBy'} = $clientsSortBy;
	$self->{'ctree'}->sort_recursive(undef);
	$self->{'ctree'}->thaw();
}

#
# Handle selection changes
#
sub selectHandler
{
	my $self = shift;
	$self->notifyObservers({'changed' => 'selection'});
}

#
# Comparison function for CTree
#
sub compare
{
	my $self = shift;
	my $ctree = shift;
	my $text1= shift;
	my $text2 = shift;

	if(!defined $self->{'connection'})	
	{
		return 0;
	}

	my $network = $self->{'connection'}->getNetwork();

	my($nodeType1, $bssid1, $mac1) = split /,/, $text1;
	my($nodeType2, $bssid2, $mac2) = split /,/, $text2;

	if($nodeType1 eq 'networkNode' && $nodeType2 eq 'networkNode')
	{
		if($networkFieldType{$self->{'networksSortBy'}} eq 'num')
		{
			return $network->{$bssid1}{$self->{'networksSortBy'}} <=> $network->{$bssid2}{$self->{'networksSortBy'}};
		}
		elsif($networkFieldType{$self->{'networksSortBy'}} eq 'str')
		{
			return $network->{$bssid1}{$self->{'networksSortBy'}} cmp $network->{$bssid2}{$self->{'networksSortBy'}};
		}
	}
	elsif ($nodeType1 eq 'clientNode' && $nodeType2 eq 'clientNode')
	{
		if($clientFieldType{$self->{'clientsSortBy'}} eq 'num')
		{
			return $network->{$bssid1}{'client'}{$mac1}{$self->{'clientsSortBy'}} <=> $network->{$bssid2}{'client'}{$mac2}{$self->{'clientsSortBy'}};
		}
		elsif($clientFieldType{$self->{'clientsSortBy'}} eq 'str')
		{
			return $network->{$bssid1}{'client'}{$mac1}{$self->{'clientsSortBy'}} cmp $network->{$bssid2}{'client'}{$mac2}{$self->{'clientsSortBy'}};
		}
	}

	return 0;
}

#
# Button press handler
#
sub buttonPressHandler
{
	my $self = shift;
	my $ctree = shift;
	my $event = shift;

	if($event->{'button'} == 3)
	{
		my($row, $column) = $ctree->get_selection_info($event->{'x'}, $event->{'y'});
		if(defined $row && defined $column)
		{
			$ctree->select_row($row, $column);
			$self->{'popup'}->popup(undef, undef, $event->{'time'}, $event->{'button'}, undef, undef);
			return $true;
		}
	}
	return $false;
}

#
# Read manufacturers data from files
#
sub readManufData
{
	my $self = shift;
	my $file = shift;
	my @manuf;
	my @fieldNames = qw(mac mask name model ssid chan ip);

	open(FILE, $file) or die "Can't open $file: $!\n";
	while(<FILE>)
	{
		chomp;
		my @fields = split '\t', $_;
		splice(@fields, 0, 1, split '\/', $fields[0]); 
		my %m;
		for(my $i = 0; $i <= $#fields; $i++)
		{
			$m{$fieldNames[$i]} = $fields[$i];
		}
		push @manuf, \%m;
	}
	close(FILE);
	return(\@manuf);
}

#
# Lookup manufacturer. Very messy.
#
sub lookupManuf
{
	my $self = shift;
	my($manufData, $mac, $ssid, $chan, $wep, $cloaked, $ip)  = (@_);

	# Is it cached?
	if(defined $self->{'manufLookupCache'}{$manufData}{$mac}{$ssid}{$chan}{$wep}{$cloaked}{$ip})
	{
		return($self->{'manufLookupCache'}{$manufData}{$mac}{$ssid}{$chan}{$wep}{$cloaked}{$ip}{'name'},
		$self->{'manufLookupCache'}{$manufData}{$mac}{$ssid}{$chan}{$wep}{$cloaked}{$ip}{'model'},
		$self->{'manufLookupCache'}{$manufData}{$mac}{$ssid}{$chan}{$wep}{$cloaked}{$ip}{'score'})
	}

	# First build list of matching entries
	my @matching;
	my $bestMaskLen = 0;
	for my $m (@{$manufData})
	{
		if($self->macAnd($m->{'mask'}, $mac) eq $m->{'mac'})
		{
			my $maskLen = $self->maskLen($m->{'mask'});
			if($maskLen > $bestMaskLen)
			{
				$bestMaskLen = $maskLen;
				@matching = ();
			}
			push @matching, $m;
		}
	}

	# If no matches, return unknowns
	if($#matching < 0)
	{
		$self->{'manufLookupCache'}{$manufData}{$mac}{$ssid}{$chan}{$wep}{$cloaked}{$ip} = {name => 'Unknown', model => 'Unknown', score => 0};
		return('Unknown', 'Unknown', 0);
	}

	# Select matches where ssid and channel match
	my $bestScore = 5;
	my $bestMatch = $matching[0];
	for my $m (@matching)
	{
		my $score = 5;
		if(defined $m->{'ssid'} && $m->{'ssid'} eq $ssid)
		{
			$score += 1;
		}
		if(defined $m->{'chan'} && $m->{'chan'} == $chan)
		{
			$score += 1;
		}
		if(defined $m->{'ip'} && $m->{'ip'} eq $ip)
		{
			$score += 1;
		}
		if($score > $bestScore)
		{
			$bestMatch = $m;
		}
		$bestScore = $score;
	}

	# Reduce score if wep of cloaking present
	if($bestMatch->{'wep'})
	{
		$bestScore -= 1;
	}
	if($bestMatch->{'cloaked'})
	{
		$bestScore -= 1;
	}

	# Update cache
	$self->{'manufLookupCache'}{$manufData}{$mac}{$ssid}{$chan}{$wep}{$cloaked}{$ip} = {name => $bestMatch->{'name'},
		model => $bestMatch->{'model'}, score => $bestScore};

	return($bestMatch->{'name'}, $bestMatch->{'model'}, $bestScore);
}

#
# Perform logical "and" on MACs
#
sub macAnd
{
	my $self = shift;
	my($a, $b) = (@_);
	my @aOctets = split(':', $a);
	my @bOctets = split(':', $b);
	my @resOctets;
	for(my $i = 0; $i <= 5; $i++)
	{
		$resOctets[$i] = sprintf("%.2X", hex($aOctets[$i]) & hex($bOctets[$i]));
	}
	my $res = join ':', @resOctets;
	return $res;
}

#
# Get netmask length
#
sub maskLen
{
	my $self = shift;
	my $mask = shift;
	my @octets = split(':', $mask);
	my $len = 0;
	for(my $i = 0; $i <= 5; $i++)
	{
		my $octet = hex($octets[$i]);
		for(my $j = 0; $j <= 7; $j++)
		{
			if(!$octet)
			{
				return($len);
			}
			$octet = ($octet << 1);
			$len++;
		}
	}
}

#
# Remove all stored data
#
sub flush
{
        my $self = shift;

	# Remove all nodes from Ctree
	$self->{'ctree'}->freeze();
	for my $n (keys %{$self->{'cnode'}})
        {
                if(defined $self->{'cnode'}{$n}{'node'})
                {
                        $self->{'ctree'}->remove_node($self->{'cnode'}{$n}{'node'});
                }
        }
	$self->{'ctree'}->thaw();

	# Delete cnodes hash
	delete $self->{'cnode'};
}

#
# Get widget
#
sub getWidget
{
	my $self = shift;
	return $self->{'ctree'};
}

#
# Handle an update from an observer
#
sub update
{
	my $self = shift;
	my $object = shift;
	my $data = shift;

	if($object->isa('Connection') && defined $self->{'connection'} && $object eq $self->{'connection'})
	{
		if($data->{'changed'} eq 'network')
		{
			my $bssid = $data->{'bssid'};
			$self->{'networkUpdateFlag'} = $true;
			$self->networkScreenUpdate($bssid);
		}
		elsif($data->{'changed'} eq 'client')
		{
			my $bssid = $data->{'bssid'};
			my $mac = $data->{'mac'};
			$self->{'clientUpdateFlag'}{$bssid} = $true;
			$self->clientScreenUpdate($bssid, $mac);
		}
		elsif($data->{'changed'} eq 'remove')
		{
			my $bssid = $data->{'bssid'};
			$self->networkScreenRemove($bssid);
		}
	}
	elsif($object->isa('Preferences') && defined $self->{'connection'})
	{
		my $network = $self->{'connection'}->getNetwork();
		$self->{'ctree'}->freeze();
		$self->flush();
		for my $bssid (keys %{$network})
		{
			$self->networkScreenUpdate($bssid);
			for my $mac (keys %{$network->{$bssid}{'client'}})
			{
				$self->clientScreenUpdate($bssid, $mac);
			}
		}
		$self->{'ctree'}->sort_recursive(undef);
		$self->{'ctree'}->thaw();
	}
}

#
# Network is updated or added
#
sub networkScreenUpdate
{
	my $self = shift;
	my $bssid = shift;

	if(!defined $self->{'connection'})
	{
		return;
	}

	$self->{'ctree'}->freeze();

	# If there is no network node create it
	if(!defined $self->{'cnode'}{$bssid}{'node'})
	{
		my $n = $self->{'ctree'}->insert_node(undef, undef,
			[$self->getNetworkFieldValue($bssid, 'SSID'), '', "networkNode,$bssid"], 5,
			$self->{'pixmap'}{$self->getNetworkPixmapName($bssid)}{'pixmap'},
			$self->{'pixmap'}{$self->getNetworkPixmapName($bssid)}{'mask'},
			$self->{'pixmap'}{$self->getNetworkPixmapName($bssid)}{'pixmap'},
			$self->{'pixmap'}{$self->getNetworkPixmapName($bssid)}{'mask'},
			$false, $false);
		$self->{'ctree'}->node_set_foreground($n, $self->getNetworkColor($bssid));
		$self->{'ctree'}->node_set_selectable($n, $true);
		if(!$self->{'ctree'}->selection())
		{
			$self->{'ctree'}->select($n);
		}
		$self->{'cnode'}{$bssid}{'node'} = $n;
	}
	# Otherwise update it
	else
	{
		my @info = $self->{'ctree'}->get_node_info($self->{'cnode'}->{$bssid}{'node'});
		$self->{'ctree'}->set_node_info($self->{'cnode'}{$bssid}{'node'},
			$self->getNetworkFieldValue($bssid, 'SSID'), 5,
			$self->{'pixmap'}{$self->getNetworkPixmapName($bssid)}{'pixmap'},
			$self->{'pixmap'}{$self->getNetworkPixmapName($bssid)}{'mask'},
			$self->{'pixmap'}{$self->getNetworkPixmapName($bssid)}{'pixmap'},
			$self->{'pixmap'}{$self->getNetworkPixmapName($bssid)}{'mask'},
			$false, $info[7]);
		$self->{'ctree'}->node_set_foreground($self->{'cnode'}{$bssid}{'node'}, $self->getNetworkColor($bssid));
	}

	# Loop through network fields
	for my $f ($self->{'gKismetApplication'}{'preferences'}->getPref('networkFieldNames'))
	{
		# If the field is shown
		if($self->{'gKismetApplication'}{'preferences'}->getPref('networkFieldStatus', $f))
		{
			# If the field node is not defined create it
			if(!defined $self->{'cnode'}{$bssid}{$f}{'node'})
			{
				my $n = $self->{'ctree'}->insert_node($self->{'cnode'}{$bssid}{'node'}, undef,
					[$f, $self->getNetworkFieldValue($bssid, $f), "networkNodeField,$bssid,$f"], 5,
					undef, undef, undef, undef, $true, $false); 
				$self->{'ctree'}->node_set_selectable($n, $false);
				$self->{'cnode'}{$bssid}{$f}{'node'} = $n;
			}
			# Otherwise update it
			else
			{
				$self->{'ctree'}->set_text($self->{'cnode'}{$bssid}{$f}{'node'}, 1,
					$self->getNetworkFieldValue($bssid, $f));
			}
		}
	}

	$self->{'ctree'}->thaw();
}

#
# Network is deleted
#
sub networkScreenRemove
{
	my $self = shift;
	my $bssid = shift;

	if(defined $self->{'cnode'}{$bssid})
	{
		$self->{'ctree'}->remove_node($self->{'cnode'}{$bssid}{'node'});
		delete $self->{'cnode'}{$bssid};
	}
}

#
# Client is updated or added
#
sub clientScreenUpdate
{
	my $self = shift;
	my $bssid = shift;
	my $mac = shift;

	if(!defined $self->{'connection'})
	{
		return;
	}

	# If network node is not defined we can't proceed
	if(!defined $self->{'cnode'}{$bssid})
	{
		return;
	}

	$self->{'ctree'}->freeze();

	# If client node is not defined create it
	if(!defined $self->{'cnode'}{$bssid}{'client'}{$mac}{'node'})
	{
		my $n = $self->{'ctree'}->insert_node($self->{'cnode'}{$bssid}{'node'}, undef,
			[$self->getClientFieldValue($bssid, $mac, 'MAC'), '', "clientNode,$bssid,$mac"], 5,
			$self->{'pixmap'}{$self->getClientPixmapName($bssid, $mac)}{'pixmap'},
			$self->{'pixmap'}{$self->getClientPixmapName($bssid, $mac)}{'mask'},
			$self->{'pixmap'}{$self->getClientPixmapName($bssid, $mac)}{'pixmap'},
			$self->{'pixmap'}{$self->getClientPixmapName($bssid, $mac)}{'mask'},
			$false, $false);
		$self->{'cnode'}{$bssid}{'client'}{$mac}{'node'} = $n;
		$self->{'ctree'}->node_set_selectable($n, $false);
		$self->networkScreenUpdate($bssid);
	}
	# otherwise update it
	else
	{
		my @info = $self->{'ctree'}->get_node_info($self->{'cnode'}{$bssid}{'client'}{$mac}{'node'});
		$self->{'ctree'}->set_node_info($self->{'cnode'}{$bssid}{'client'}{$mac}{'node'},
			$self->getClientFieldValue($bssid, $mac, 'MAC'), 5,
			$self->{'pixmap'}{$self->getClientPixmapName($bssid, $mac)}{'pixmap'},
			$self->{'pixmap'}{$self->getClientPixmapName($bssid, $mac)}{'mask'},
			$self->{'pixmap'}{$self->getClientPixmapName($bssid, $mac)}{'pixmap'},
			$self->{'pixmap'}{$self->getClientPixmapName($bssid, $mac)}{'mask'},
			$false, $info[7]);
	}

	# Loop through client fields
	for my $f ($self->{'gKismetApplication'}{'preferences'}->getPref('clientFieldNames'))
	{
		# If the field is shown
		if($self->{'gKismetApplication'}{'preferences'}->getPref('clientFieldStatus', $f))
		{
			# If the client field is not defined create it
			if(!defined $self->{'cnode'}{$bssid}{'client'}{$mac}{$f}{'node'})
			{
				my $n = $self->{'ctree'}->insert_node($self->{'cnode'}{$bssid}{'client'}{$mac}{'node'}, undef,
					[$f, $self->getClientFieldValue( $bssid, $mac, $f), "clientNodeField,$bssid,$mac,$f"], 5,
					undef, undef, undef, undef, $true, $false);
				$self->{'cnode'}{$bssid}{'client'}{$mac}{$f}{'node'} = $n;
				$self->{'ctree'}->node_set_selectable($n, $false);
			}
			# otherwise update it
			else
			{
				$self->{'ctree'}->set_text($self->{'cnode'}{$bssid}{'client'}{$mac}{$f}{'node'}, 1,
					$self->getClientFieldValue($bssid, $mac, $f));
			}
		}
	}

	$self->{'ctree'}->thaw();
}

#
# Get colors
#
sub buildColors
{
	my $self = shift;
	my $colormap = $self->{'ctree'}->get_colormap();

	my $colorYellow = Gtk::Gdk::Color->parse_color("gold3");
	$colorYellow = $colormap->color_alloc($colorYellow);
	$self->{'colorYellow'} = $colorYellow;

	my $colorGreen = Gtk::Gdk::Color->parse_color("green3");
	$colorGreen = $colormap->color_alloc($colorGreen);
	$self->{'colorGreen'} = $colorGreen;

	my $colorRed = Gtk::Gdk::Color->parse_color("red2");
	$colorRed = $colormap->color_alloc($colorRed);
	$self->{'colorRed'} = $colorRed;

	my $colorBlack = Gtk::Gdk::Color->parse_color("black");
	$colorBlack = $colormap->color_alloc($colorBlack);
	$self->{'colorBlack'} = $colorBlack;
}

#
# Collapse selected nodes
#
sub collapse
{
	my $self = shift;
	my (@selection) = $self->{'ctree'}->selection();
	for my $selected (@selection)
	{
		$self->{'ctree'}->collapse_recursive($selected);
	}
}

#
# Expand selected nodes
#
sub expand
{
	my $self = shift;
	my (@selection) = $self->{'ctree'}->selection();
	for my $selected (@selection)
	{
		$self->{'ctree'}->expand_recursive($selected);
	}
}

#
# Get selected network bssid
#
sub getSelectedNetworks
{
	my $self = shift;
        my (@selection) = $self->{'ctree'}->selection();
	my @networks;
	for my $selected (@selection)
	{
		my $data = $self->{'ctree'}->node_get_text($selected, 2);
		my @fields = split /,/, $data;
		if($fields[0] eq 'networkNode')
		{
			push @networks, $fields[1];
		}
	}
	return @networks;
}

#
# Return printable network data
#
sub getNetworkFieldValue
{
	my $self = shift;
	my $bssid = shift;
	my $field = shift;

	if(!defined $self->{'connection'})
	{
		return undef;
	}
	my $network = $self->{'connection'}->getNetwork();

	if($field eq 'SSID')
	{
		my $ssid = $network->{$bssid}{'ssid'};
		if($network->{$bssid}{'cloaked'})
		{
			$ssid = '<' . $ssid . '>';
		}
		return $ssid;
	}
	elsif($field eq 'BSSID')
	{
		return $network->{$bssid}{'bssid'};
	}
	elsif($field eq 'Beacon info')
	{
		return $network->{$bssid}{'beaconinfo'};
	}
	elsif($field eq 'Carrier')
	{
		if($network->{$bssid}{'carrierset'} & (1 << $carrier_type{'carrier_unknown'}))
		{
			return 'Unknown';
		}
		elsif($network->{$bssid}{'carrierset'} & (1 << $carrier_type{'carrier_80211b'}))
		{
			return 'IEEE 802.11b';
		}
		elsif($network->{$bssid}{'carrierset'} & (1 << $carrier_type{'carrier_80211bplus'}))
		{
			return 'IEEE 802.11b+';
		}
		elsif($network->{$bssid}{'carrierset'} & (1 << $carrier_type{'carrier_80211a'}))
		{
			return 'IEEE 802.11a';
		}
		elsif($network->{$bssid}{'carrierset'} & (1 << $carrier_type{'carrier_80211g'}))
		{
			return 'IEEE 802.11g';
		}
		elsif($network->{$bssid}{'carrierset'} & (1 << $carrier_type{'carrier_80211fhss'}))
		{
			return 'IEEE 802.11 FHSS';
		}
		elsif($network->{$bssid}{'carrierset'} & (1 << $carrier_type{'carrier_80211dsss'}))
		{
			return 'IEEE 802.11 DSSS';
		}
		else
		{
			return 'Unknown';
		}
	}
	elsif($field eq 'Encoding')
	{
		if($network->{$bssid}{'encodingset'} & (1 << $encoding_type{'encoding_unknown'}))
		{
			return 'Unknown';
		}
		elsif($network->{$bssid}{'encodingset'} & (1 << $encoding_type{'encoding_cck'}))
		{
			return 'CCK';
		}
		elsif($network->{$bssid}{'encodingset'} & (1 << $encoding_type{'encoding_pbcc'}))
		{
			return 'PBCC';
		}
		elsif($network->{$bssid}{'encodingset'} & (1 << $encoding_type{'encoding_ofdm'}))
		{
			return 'OFDM';
		}
		else
		{
			return 'Unknown';
		}
	}
	elsif($field eq 'Manufacturer')
	{
		my($manuf, $model, $score) = $self->lookupManuf(
			$network->{$bssid}{'type'} == $network_type{'network_adhoc'} || $network->{$bssid}{'type'} == $network_type{'network_probe'} ?
			$self->{'clManuf'} : $self->{'apManuf'},
			$bssid, $network->{$bssid}{'ssid'}, $network->{$bssid}{'channel'}, $network->{$bssid}{'wep'}, $network->{$bssid}{'cloaked'},
			$network->{$bssid}{'rangeip'});
		return($manuf);
	}
	elsif($field eq 'Model')
	{
		my($manuf, $model, $score) = $self->lookupManuf(
			$network->{$bssid}{'type'} == $network_type{'network_adhoc'} || $network->{$bssid}{'type'} == $network_type{'network_probe'} ?
			$self->{'clManuf'} : $self->{'apManuf'},
			$bssid, $network->{$bssid}{'ssid'}, $network->{$bssid}{'channel'}, $network->{$bssid}{'wep'}, $network->{$bssid}{'cloaked'},
			$network->{$bssid}{'rangeip'});
		return($model);
	}
	elsif($field eq 'Max rate')
	{
		return $network->{$bssid}{'maxrate'};
	}
	elsif($field eq "Max seen")
	{
		return $network->{$bssid}{'maxseenrate'} * 100;
	}
	elsif($field eq 'First seen')
	{
		return scalar localtime($network->{$bssid}{'firsttime'});
	}
	elsif($field eq 'Last seen')
	{
		return scalar localtime($network->{$bssid}{'lasttime'});
	}
	elsif($field eq 'Clients')
	{
		my @clients = keys %{$network->{$bssid}{'client'}};
		return $#{clients} + 1;
	}
	elsif($field eq 'Type')
	{
		if($network->{$bssid}{'type'} == $network_type{'network_ap'})
                {
                        return 'Access Point (infrastructure)';
                }
                elsif($network->{$bssid}{'type'} == $network_type{'network_adhoc'})
                {
                        return 'Ad-hoc';
                }
                elsif($network->{$bssid}{'type'} == $network_type{'network_probe'})
                {
                        return 'Probe request (searching client)';
                }
                elsif($network->{$bssid}{'type'} == $network_type{'network_turbocell'})
                {
                        return 'Turbocell';
                }
                elsif($network->{$bssid}{'type'} == $network_type{'network_data'})
                {
                        return 'Data (no network control traffic)';
                }
                elsif($network->{$bssid}{'type'} == $network_type{'network_remove'})
                {
                        return 'Network remove request';
                }
	}
	elsif($field eq 'Channel')
	{
		return $network->{$bssid}{'channel'};
	}
	elsif($field eq 'WEP')
	{
		$network->{$bssid}{'wep'} ? return 'Yes' : return 'No';
	}
	elsif($field eq 'Encryption')
	{
		if($network->{$bssid}{'wep'} == $encryption_type{'encryption_none'})
		{
			return 'None';
		}

		my $res = '';
		if($network->{$bssid}{'wep'} & $encryption_type{'encryption_unknown'})
		{
			$res .= 'Unknown ';
		}
		if($network->{$bssid}{'wep'} & $encryption_type{'encryption_wep'})
		{
			$res .= 'WEP ';
		}
		if($network->{$bssid}{'wep'} & $encryption_type{'encryption_layer3'})
		{
			$res .= 'Layer 3 ';
		}
		if($network->{$bssid}{'wep'} & $encryption_type{'encryption_wpa'})
		{
			$res .= 'WPA ';
		}
		if($network->{$bssid}{'wep'} & $encryption_type{'encryption_leap'})
		{
			$res .= 'LEAP ';
		}
		if($network->{$bssid}{'wep'} & $encryption_type{'encryption_ttls'})
		{
			$res .= 'TTLS ';
		}
		if($network->{$bssid}{'wep'} & $encryption_type{'encryption_tls'})
		{
			$res .= 'TLS ';
		}
		if($network->{$bssid}{'wep'} & $encryption_type{'encryption_peap'})
		{
			$res .= 'PEAP ';
		}
		if($network->{$bssid}{'wep'} & $encryption_type{'encryption_isakmp'})
		{
			$res .= 'ISAKMP ';
		}
		if($network->{$bssid}{'wep'} & $encryption_type{'encryption_pptp'})
		{
			$res .= 'PPTP ';
		}
		return substr($res, 0, -1);
	}
	elsif($field eq "Decrypted")
	{
		return $network->{$bssid}{'decrypted'} ? 'Yes' : 'No';
	}
	elsif($field eq 'Beacon')
	{
		return $network->{$bssid}{'beaconrate'};
	}
	elsif($field eq 'Data packets')
	{
		return $network->{$bssid}{'datapackets'};
	}
	elsif($field eq 'LLC packets')
	{
		return $network->{$bssid}{'llcpackets'};
	}
	elsif($field eq 'Crypt packets')
	{
		return $network->{$bssid}{'cryptpackets'};
	}
	elsif($field eq 'Weak packets')
	{
		return $network->{$bssid}{'weakpackets'};
	}
	elsif($field eq 'Dupe IV')
	{
		return $network->{$bssid}{'dupeivpackets'};
	}
	elsif($field eq 'Data')
	{
		return $network->{$bssid}{'datasize'} . " bytes";
	}
	elsif($field eq 'Power')
	{
		return $network->{$bssid}{'signal'} . "/" . $network->{$bssid}{'bestsignal'};
	}
	elsif($field eq 'Noise')
	{
		return $network->{$bssid}{'noise'} . "/" . $network->{$bssid}{'bestnoise'};
	}
	elsif($field eq 'IP Range')
	{
		return $network->{$bssid}{'rangeip'};
	}
	elsif($field eq 'IP Type')
	{
		if($network->{$bssid}{'atype'} == $address_type{'address_none'})
                {
                        return 'None detected';
                }
                elsif($network->{$bssid}{'atype'} == $address_type{'address_factory'})
                {
                        return 'Factory default';
                }
                elsif($network->{$bssid}{'atype'} == $address_type{'address_udp'})
                {
                        return 'UDP (' .  $network->{$bssid}{'octets'} . " octets)";
                }
                elsif($network->{$bssid}{'atype'} == $address_type{'address_arp'})
                {
                        return 'ARP (' .  $network->{$bssid}{'octets'} . " octets)";
                }
                elsif($network->{$bssid}{'atype'} == $address_type{'address_tcp'})
                {
                        return 'TCP (' .  $network->{$bssid}{'octets'} . " octets)";
                }
                elsif($network->{$bssid}{'atype'} == $address_type{'address_dhcp'})
                {
                        return 'DHCP';
                }
                elsif($network->{$bssid}{'atype'} == $address_type{'address_group'})
                {
                        return 'Group (aggregate)';
                }
                else
                {
                        return 'UNKNOWN';
                }
	}
	else
	{
		return('Not implemented');
	}
}

#
# Return printable client data
#
sub getClientFieldValue
{
	my $self = shift;
	my $bssid = shift;
	my $mac = shift;
	my $field = shift;

	if(!defined $self->{'connection'})
	{
		return undef;
	}
	my $network = $self->{'connection'}->getNetwork();

	if($field eq 'MAC')
	{
		return $network->{$bssid}{'client'}{$mac}{'mac'};
	}
	elsif($field eq 'Type')
	{
		if($network->{$bssid}{'client'}{$mac}{'type'} == $client_type{'client_unknown'})
		{
			return 'Unknown';
		}
		elsif ($network->{$bssid}{'client'}{$mac}{'type'} == $client_type{'client_fromds'})
		{
			return 'From Distribution (AP->Wireless)';
		}
		elsif ($network->{$bssid}{'client'}{$mac}{'type'} == $client_type{'client_tods'})
		{
			return 'To Distribution (Wireless->AP)';
		}
		elsif ($network->{$bssid}{'client'}{$mac}{'type'} == $client_type{'client_interds'})
		{
			return 'Intra-distribution (AP->AP)';
		}
		elsif ($network->{$bssid}{'client'}{$mac}{'type'} == $client_type{'client_established'})
		{
			return 'Established (Wireless->AP and AP->Wireless)';
		}
		else
		{
			return 'Unknown';
		}
	}
	elsif($field eq 'Manufacturer')
	{
		my($manuf, $model, $score) = $self->lookupManuf($self->{'clManuf'}, $mac, $network->{$bssid}{'ssid'},
			$network->{$bssid}{'channel'}, $network->{$bssid}{'wep'}, $network->{$bssid}{'cloaked'}, $network->{$bssid}{'client'}{$mac}{'ip'});
		return($manuf);
	}
	elsif($field eq 'Model')
	{
		my($manuf, $model, $score) = $self->lookupManuf($self->{'clManuf'}, $mac, $network->{$bssid}{'ssid'},
			$network->{$bssid}{'channel'}, $network->{$bssid}{'wep'}, $network->{$bssid}{'cloaked'}, $network->{$bssid}{'client'}{$mac}{'ip'});
		return($model);
	}
	elsif($field eq 'First seen')
	{
		return scalar localtime $network->{$bssid}{'client'}{$mac}{'firsttime'};
	}
	elsif($field eq 'Last seen')
	{
		return scalar localtime $network->{$bssid}{'client'}{$mac}{'lasttime'};
	}
	elsif($field eq 'Max rate')
	{
		return $network->{$bssid}{'client'}{$mac}{'maxrate'};
	}
	elsif($field eq "Max seen")
	{
		return $network->{$bssid}{'client'}{$mac}{'maxseenrate'} * 100;
	}
	elsif($field eq 'Encoding')
	{
		if($network->{$bssid}{'client'}{$mac}{'encodingset'} & (1 << $encoding_type{'encoding_unknown'}))
		{
			return 'Unknown';
		}
		elsif($network->{$bssid}{'client'}{$mac}{'encodingset'} & (1 << $encoding_type{'encoding_cck'}))
		{
			return 'CCK';
		}
		elsif($network->{$bssid}{'client'}{$mac}{'encodingset'} & (1 << $encoding_type{'encoding_pbcc'}))
		{
			return 'PBCC';
		}
		elsif($network->{$bssid}{'client'}{$mac}{'encodingset'} & (1 << $encoding_type{'encoding_ofdm'}))
		{
			return 'OFDM';
		}
		else
		{
			return 'Unknown';
		}
	}
	elsif($field eq 'Channel')
	{
		return $network->{$bssid}{'channel'};
	}
	elsif($field eq 'WEP')
	{
		$network->{$bssid}{'client'}{$mac}{'wep'} ? return 'Yes' : return 'No';
	}
	elsif($field eq "Decrypted")
	{
		return $network->{$bssid}{'client'}{$mac}{'decrypted'} ? 'Yes' : 'No';
	}
	elsif($field eq 'IP')
	{
		$network->{$bssid}{'client'}{$mac}{'ip'};
	}
	elsif($field eq 'Data packets')
	{
		$network->{$bssid}{'client'}{$mac}{'datapackets'};
	}
	elsif($field eq 'Crypt packets')
	{
		$network->{$bssid}{'client'}{$mac}{'cryptpackets'};
	}
	elsif($field eq 'Weak packets')
	{
		$network->{$bssid}{'client'}{$mac}{'weakpackets'};
	}
	elsif($field eq 'Data')
	{
		$network->{$bssid}{'client'}{$mac}{'datasize'} . " bytes";
	}
	elsif($field eq 'Power')
	{
		return $network->{$bssid}{'client'}{$mac}{'signal'} . "/" . $network->{$bssid}{'client'}{$mac}{'bestsignal'};
	}
	elsif($field eq 'Noise')
	{
		return $network->{$bssid}{'client'}{$mac}{'noise'} . "/" . $network->{$bssid}{'client'}{$mac}{'bestnoise'};
	}
	else
	{
		return('Not implemented');
	}
}

#
# Return color for a network
#
sub getNetworkColor
{
	my $self = shift;
	my $bssid = shift;

	if(!defined $self->{'connection'})
	{
		return undef;
	}
	my $network = $self->{'connection'}->getNetwork();

	if(!$network->{$bssid}{'wep'})
	{
		my($manuf, $model, $score) = $self->lookupManuf($self->{'apManuf'}, $bssid, $network->{$bssid}{'ssid'},
			$network->{$bssid}{'channel'}, $network->{$bssid}{'wep'}, $network->{$bssid}{'cloaked'}, $network->{$bssid}{'rangeip'});
		if($score == $max_manuf_score)
		{
			return($self->{'colorRed'});
		}
		return($self->{'colorYellow'});
	}
	else
	{
		return($self->{'colorGreen'});
	}
}

#
# Get network pixmap
#
sub getNetworkPixmapName
{
	my $self = shift;
	my $bssid = shift;
 
	if(!defined $self->{'connection'})
	{
		return undef;
	}
	my $network = $self->{'connection'}->getNetwork();

	$self->buildPixmaps();
	my $pixmapName;

	if($network->{$bssid}{'type'} == $network_type{'network_ap'})
        {
		$pixmapName = 'networkAP';
	}
	elsif($network->{$bssid}{'type'} == $network_type{'network_adhoc'})
	{
		$pixmapName = 'networkAdhoc';
	}
	elsif($network->{$bssid}{'type'} == $network_type{'network_probe'})
	{
		$pixmapName = 'networkProbe';
	}
	elsif($network->{$bssid}{'type'} == $network_type{'network_turbocell'})
	{
		$pixmapName = 'networkTurbocell';
	}
	elsif($network->{$bssid}{'type'} == $network_type{'network_data'})
	{
		$pixmapName = 'networkData';
  	}
	elsif($network->{$bssid}{'type'} == $network_type{'network_remove'})
	{
		$pixmapName = 'networkRemove';
	}

	if(time() - $network->{$bssid}{'lasttime'} < $activeTimeout)
	{
		$pixmapName .= 'Active';
	}

	return $pixmapName;
}
 
#
# Get client pixmap
#
sub getClientPixmapName
{
	my $self = shift;
	my $bssid = shift;
	my $mac = shift;
 
	if(!defined $self->{'connection'})
	{
		return undef;
	}
	my $network = $self->{'connection'}->getNetwork();

	$self->buildPixmaps();
	my $pixmapName;

	if($network->{$bssid}{'client'}{$mac}{'type'} == $client_type{'client_unknown'})
	{
		$pixmapName = 'clientUnknown';
	}
	elsif ($network->{$bssid}{'client'}{$mac}{'type'} == $client_type{'client_fromds'})
	{
		$pixmapName = 'clientFromDist';
	}
	elsif ($network->{$bssid}{'client'}{$mac}{'type'} == $client_type{'client_tods'})
	{
		$pixmapName = 'clientToDist';
	}
	elsif ($network->{$bssid}{'client'}{$mac}{'type'} == $client_type{'client_interds'})
	{
		$pixmapName = 'clientIntraDist';
	}
	elsif ($network->{$bssid}{'client'}{$mac}{'type'} == $client_type{'client_established'})
	{
		$pixmapName = 'clientEstablished';
	}

	if(time() - $network->{$bssid}{'client'}{$mac}{'lasttime'} < $activeTimeout)
	{
		$pixmapName .= 'Active';
	}

	return $pixmapName;
}

#
# Build pixmaps if not yet built
#
sub buildPixmaps
{
	my $self = shift;
	if(!$self->{'pixmapsBuilt'})
	{
		for my $pixmap (qw(networkAP networkAdhoc networkProbe networkTurbocell networkData networkRemove 
			clientFromDist clientToDist clientIntraDist clientEstablished clientUnknown
			networkAPActive networkAdhocActive networkProbeActive networkTurbocellActive networkDataActive networkRemoveActive
			clientFromDistActive clientToDistActive clientIntraDistActive clientEstablishedActive clientUnknownActive))
		{
			($self->{'pixmap'}{$pixmap}{'pixmap'}, $self->{'pixmap'}{$pixmap}{'mask'}) =
				Gtk::Gdk::Pixmap->create_from_xpm($self->{'ctree'}->window(),
				Gtk::Widget->get_default_style()->bg('normal'),
				$self->{'gKismetApplication'}{'path'}{'xpm'} . '/' . $pixmap . '.xpm');
		}
		$self->{'pixmapsBuilt'} = $true;
	}
}

1;
